package com.google.code.biglo.runtime;

import java.util.Date;
import java.util.List;

import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.code.biglo.dao.ActivityDao;
import com.google.code.biglo.dao.ActivityState;
import com.google.code.biglo.dao.BpmnDao;
import com.google.code.biglo.dao.InstanceDao;
import com.google.code.biglo.dao.InstanceState;
import com.google.code.biglo.engine.BpmnEngineImpl;
import com.google.code.biglo.lang.BpmnDefinition;
import com.google.code.biglo.lang.bpmn.model.TFlowNode;
import com.google.code.biglo.lang.bpmn.model.TScriptTask;
import com.google.code.biglo.lang.bpmn.model.TSequenceFlow;
import com.google.code.biglo.runtime.flownode.FlowNode;

public class BpmnInstance {
    private static Log log = LogFactory.getLog(BpmnInstance.class);
    
	private InstanceDao instanceDao;
	private BpmnDao bpmnDao;
	private BpmnDefinition bpmnDefinition;
	
	public static BpmnInstance create(BpmnDefinition bpmn, BpmnDao dao) {
		BpmnInstance bi = new BpmnInstance();
		bi.bpmnDao = dao;
		bi.instanceDao = new InstanceDao();
		bi.instanceDao.setProcessRef(bpmn.getId());
		bi.instanceDao.setState(InstanceState.NEW);
		dao.persist(bi.instanceDao);
		bi.bpmnDefinition = bpmn;
		return bi;
	}
	
	private TFlowNode getNode(String name) {
		TFlowNode v = bpmnDefinition.nodes.get(name);
		Validate.notNull(v, "Can't find node " + name + " in " + bpmnDefinition.getId());
		return v;
	}
	
	public void execute(Date executionDate) {		
		while (true) {
			if (instanceDao.getState() == InstanceState.NEW) {
				instanceDao.setState(InstanceState.ACTIVE);
				for (TFlowNode node : bpmnDefinition.nodes.values()) {
					ActivityDao act = new ActivityDao();
					act.setInstance(instanceDao.getId());
					act.setName(node.getId());
					act.setParent(null);
					act.setState(ActivityState.ENABLED);
					bpmnDao.persist(act);
				}
				continue;
			} else if (instanceDao.getState() == InstanceState.ACTIVE) {
				List<ActivityDao> toExecute = bpmnDao.findActivityToExecute(instanceDao.getId());
				if (toExecute.size() == 0) {
					return;
				} else {
					ActivityDao act = toExecute.get(0);
					log.debug("Executing " + act.getState() + " " + act.getName() + " " + act.getId() + " " + act.getInstance());
					if (act.getState() == ActivityState.ENABLED) {
						FlowNode n = FlowNode.create(act, getNode(act.getName()), this);
						n.setExecutionDate(executionDate);
						n.activate();
					} else if (act.getState() == ActivityState.COMPLETING) {
						act.setState(ActivityState.COMPLETED);
						List<TSequenceFlow> l = bpmnDefinition.getOutgoing(act.getName());
						executeOutgoingTransitions(act, l);
						verifyScopeCompleted(act.getParent());
					} else {
						throw new IllegalStateException();
					}
				}
			} else if (instanceDao.getState() == InstanceState.COMPLETED) {
				return;
			} else {
				throw new IllegalStateException("Unknown state " + instanceDao.getState());
			}
		}
	}
	
	private void verifyScopeCompleted(Long parentId) {
		Long l = bpmnDao.countActivitiesToExecuteInScope(instanceDao.getId(), parentId);
		if (l == 0) {
			if (parentId == null) {
				instanceDao.setState(InstanceState.COMPLETED);
			} else {
				completeScope(parentId, null);
			}
		}
	}
	
	private void completeScope(Long parentId, Object faultInfo) {
		if (faultInfo == null) {
			ActivityDao parent = bpmnDao.find(ActivityDao.class, parentId);
			Validate.isTrue(parent.getState() == ActivityState.ACTIVE);
			parent.setState(ActivityState.COMPLETING);
		} else {
			throw new IllegalStateException();
		}
	}

	private void executeOutgoingTransitions(ActivityDao act, List<TSequenceFlow> outgoingList) {
		for (TSequenceFlow outgoing : outgoingList) {
			String target = outgoing.getTargetRef();
			ActivityDao targetAct = new ActivityDao();
			targetAct.setParent(act.getParent());
			targetAct.setInstance(act.getInstance());
			targetAct.setName(target);
			targetAct.setState(ActivityState.ENABLED);
			bpmnDao.persist(targetAct);
		}
	}
	
	public InstanceDao getInstanceDao() {
		return instanceDao;
	}	
}
